<?php
// 应用公共文件

/**
 * 强大的打印调试函数
 * https://symfony.com/doc/current/components/var_dumper.html
 * @param $var
 * @return array
 */
function d($var)
{
    foreach (func_get_args() as $v) {
        if (is_string($v) || is_numeric($v)) {
            $v = new \ErrorException(
                $v, 1, E_WARNING
            );
        }
        Symfony\Component\VarDumper\VarDumper::dump($v);
    }
    if (1 < func_num_args()) {
        return func_get_args();
    }
    return $var;
}

/**
 * 检测是postman才打印的函数
 * author: xiaogang.zhou@qq.com
 * datetime: 2020/4/23 13:29
 * @param $var
 * @param bool $die
 */
function pdd($var, $die = true)
{
    $check = \think\facade\Request::header('Postman-Token');
    if (empty($check)) {
        return;
    }
    $die && dd($var);
    d($var);
}

/**
 * 创建原生redis对象，非原生的会有一个zscan死循环的bug
 * bug：https://github.com/phpredis/phpredis/issues/1402
 * @return null|Redis
 */
function redis()
{
    static $_redis = null;
    if (!is_null($_redis)) {
        return $_redis;
    }
    $_redis = new \libs\Redis();
    $config = config('cache.stores.redis');
    $_redis->connect($config['host'], $config['port']);
    return $_redis;
}

/**
 * 计算出两个日期之间的月份
 * @param  [type] $start_date [开始日期，如2014-03]
 * @param  [type] $end_date   [结束日期，如2015-12]
 * @param string $explode [年份和月份之间分隔符，此例为 - ]
 * @param boolean $addOne [算取完之后最后是否加一月，用于算取时间戳用]
 * @return [type]             [返回是两个月份之间所有月份字符串]
 * @author Eric
 */
function dateMonths($start_date, $end_date, $explode = '-', $addOne = false)
{
    //判断两个时间是不是需要调换顺序
    $start_int = strtotime($start_date);
    $end_int = strtotime($end_date);
    if ($start_int > $end_int) {
        $tmp = $start_date;
        $start_date = $end_date;
        $end_date = $tmp;
    }


    //结束时间月份+1，如果是13则为新年的一月份
    $start_arr = explode($explode, $start_date);
    $start_year = intval($start_arr[0]);
    $start_month = intval($start_arr[1]);


    $end_arr = explode($explode, $end_date);
    $end_year = intval($end_arr[0]);
    $end_month = intval($end_arr[1]);


    $data = array();
    $data[] = $start_date;


    $tmp_month = $start_month;
    $tmp_year = $start_year;


    //如果起止不相等，一直循环
    while (!(($tmp_month == $end_month) && ($tmp_year == $end_year))) {
        $tmp_month++;
        //超过十二月份，到新年的一月份
        if ($tmp_month > 12) {
            $tmp_month = 1;
            $tmp_year++;
        }
        $data[] = $tmp_year . $explode . str_pad($tmp_month, 2, '0', STR_PAD_LEFT);
    }


    if ($addOne == true) {
        $tmp_month++;
        //超过十二月份，到新年的一月份
        if ($tmp_month > 12) {
            $tmp_month = 1;
            $tmp_year++;
        }
        $data[] = $tmp_year . $explode . str_pad($tmp_month, 2, '0', STR_PAD_LEFT);
    }


    return $data;
}


/**
 * 下载远程图片
 * @param $url
 * @param bool $type true，使用原名 false重命名
 * @return string
 */
function downloadImage($url, $type = false, $ext = '')
{
    $extension = pathinfo($url)['extension'];
    $ext = $extension ? '.' . $extension : '.jpg';
    $filename = $type ? pathinfo($url, PATHINFO_BASENAME) : md5(time() . rand(1, 10000)) . $ext;
    $filePath = app()->getRootPath() . 'public/storage/images/' . $filename;
    $bt = file_get_contents($url);
    if (empty($bt)) {
        return '';
    }
    file_put_contents($filePath, $bt);
    return $filePath;
}

//生成无限极分类树
function make_tree($arr, $tag = 'p_id', $level = 0)
{
    $refer = array();
    $tree = array();
    foreach ($arr as $k => $v) {
        $refer[$v['ID']] = &$arr[$k]; //创建主键的数组引用
    }
    foreach ($arr as $k => $v) {
        $pid = $v[$tag];  //获取当前分类的父级id
        if ($pid == 0) {
            $tree[] = &$arr[$k];  //顶级栏目
        } else {
            if (isset($refer[$pid])) {
                $refer[$pid]['children'][] = &$arr[$k]; //如果存在父级栏目，则添加进父级栏目的子栏目数组中
            }
        }
    }
    return $tree;
}

/**
 * 根据起点坐标和终点坐标测距离
 * @param  [array]   $from    [起点坐标(经纬度),例如:array(118.012951,36.810024)]
 * @param  [array]   $to    [终点坐标(经纬度)]
 * @param  [bool]    $km        是否以公里为单位 false:米 true:公里(千米)
 * @param  [int]     $decimal   精度 保留小数位数
 * @return [string]  距离数值
 */
function getDistance($from, $to, $km = true, $decimal = 2)
{
    sort($from);
    sort($to);
    $EARTH_RADIUS = 6370.996; // 地球半径系数

    $distance = $EARTH_RADIUS * 2 * asin(sqrt(pow(sin(($from[0] * pi() / 180 - $to[0] * pi() / 180) / 2), 2) + cos($from[0] * pi() / 180) * cos($to[0] * pi() / 180) * pow(sin(($from[1] * pi() / 180 - $to[1] * pi() / 180) / 2), 2))) * 1000;

    if ($km) {
        $distance = $distance / 1000;
    }

    return round($distance, $decimal);
}

function getOrderSn()
{
    $yCode = array('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J');
    $orderSn = $yCode[intval(date('Y')) - 2011] . strtoupper(dechex(date('m'))) . date('d') . substr(time(), -5) . substr(microtime(), 2, 5) . sprintf('%02d', rand(0, 99));
    return $orderSn;
}

function authCode($string, $operation = 'DECODE', $key = '', $expiry = 0)
{
    // 动态密匙长度，相同的明文会生成不同密文就是依靠动态密匙
    $ckey_length = 4;

    // 密匙
    $key = md5($key);

    // 密匙a会参与加解密
    $keya = md5(substr($key, 0, 16));
    // 密匙b会用来做数据完整性验证
    $keyb = md5(substr($key, 16, 16));
    // 密匙c用于变化生成的密文
    $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length) : substr(md5(microtime()), -$ckey_length)) : '';
    // 参与运算的密匙
    $cryptkey = $keya . md5($keya . $keyc);
    $key_length = strlen($cryptkey);
    // 明文，前10位用来保存时间戳，解密时验证数据有效性，10到26位用来保存$keyb(密匙b)，
    //解密时会通过这个密匙验证数据完整性
    // 如果是解码的话，会从第$ckey_length位开始，因为密文前$ckey_length位保存 动态密匙，以保证解密正确
    $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0) . substr(md5($string . $keyb), 0, 16) . $string;
    $string_length = strlen($string);
    $result = '';
    $box = range(0, 255);
    $rndkey = array();
    // 产生密匙簿
    for ($i = 0; $i <= 255; $i++) {
        $rndkey[$i] = ord($cryptkey[$i % $key_length]);
    }
    // 用固定的算法，打乱密匙簿，增加随机性，好像很复杂，实际上对并不会增加密文的强度
    for ($j = $i = 0; $i < 256; $i++) {
        $j = ($j + $box[$i] + $rndkey[$i]) % 256;
        $tmp = $box[$i];
        $box[$i] = $box[$j];
        $box[$j] = $tmp;
    }
    // 核心加解密部分
    for ($a = $j = $i = 0; $i < $string_length; $i++) {
        $a = ($a + 1) % 256;
        $j = ($j + $box[$a]) % 256;
        $tmp = $box[$a];
        $box[$a] = $box[$j];
        $box[$j] = $tmp;
        // 从密匙簿得出密匙进行异或，再转成字符
        $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
    }
    if ($operation == 'DECODE') {
        // 验证数据有效性，请看未加密明文的格式
        if ((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)) {
            return substr($result, 26);
        } else {
            return '';
        }
    } else {
        // 把动态密匙保存在密文里，这也是为什么同样的明文，生产不同密文后能解密的原因
        // 因为加密后的密文可能是一些特殊字符，复制过程可能会丢失，所以用base64编码
        return $keyc . str_replace('=', '', base64_encode($result));
    }
}

function object_array($array)
{
    if (is_object($array)) {
        $array = (array)$array;
    }
    if (is_array($array)) {
        foreach ($array as $key => $value) {
            $array[$key] = object_array($value);
        }
    }
    return $array;
}